DBus 自省 XML 生成 Qt 代码
各种支持 DBus 的开发框架都能够通过 XML 自动生成代码,例如 Glib 的 gdbus-codegen 和 Qt 的 qdbusxml2cpp。
通过 DBus 对象 org.freedesktop.DBus.Introspectable 接口下的 Introspect 方法可以自省 XML,这样就不需要手写了。
但是 d-feet、dbus-send 等工具会给返回值加上类型标注或者换行符导致需要人工修改。因此需要自己写一个脚本来自省 DBus。
使用示例:
1$ ./introspect.py -t system -n org.freedesktop.NetworkManager -p /org/freedesktop/NetworkManager
2$ ./introspect.py -t system -n org.freedesktop.NetworkManager -p /org/freedesktop/NetworkManager/Devices/5
3$ ls -1
4introspect.py
5org.freedesktop.NetworkManager.Device.Statistics.xml
6org.freedesktop.NetworkManager.Device.WifiP2P.xml
7org.freedesktop.NetworkManager.Device.xml
8org.freedesktop.NetworkManager.xml
9$ qdbusxml2cpp -c NetworkManager -p NetworkManager org.freedesktop.NetworkManager.xml --no-namespaces
10$ qdbusxml2cpp -c Device -p Device org.freedesktop.NetworkManager.Device.xml --no-namespaces
11$ qdbusxml2cpp -c WifiP2P -p WifiP2P org.freedesktop.NetworkManager.Device.WifiP2P.xml --no-namespaces
12$ ls -1
13Device.cpp
14Device.h
15introspect.py
16NetworkManager.cpp
17NetworkManager.h
18org.freedesktop.NetworkManager.Device.Statistics.xml
19org.freedesktop.NetworkManager.Device.WifiP2P.xml
20org.freedesktop.NetworkManager.Device.xml
21org.freedesktop.NetworkManager.xml
22WifiP2P.cpp
23WifiP2P.h使用
--no-namespaces选项的原因是,qdbusxml2cpp会把 DBus Name 的最后一项作为类名,而之前项作为命名空间名 即org.freedesktop.NetworkManager生成::org::freedesktop::NetworkManager类。 而org.freedesktop.NetworkManager.Device生成的::org::freedesktop::NetworkManager::Device类。 这两者会发生冲突。前者的NetworkManager是类名,而后者的是命名空间明。
验证生成的代码:
1#include <QDebug>
2#include "NetworkManager.h"
3#include "Device.h"
4#include "WifiP2P.h"
5
6int main(void)
7{
8 NetworkManager networkManager{"org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager", QDBusConnection::systemBus()};
9 auto devicePathes = networkManager.devices();
10 for (auto& devicePath : devicePathes)
11 {
12 Device device("org.freedesktop.NetworkManager", devicePath.path(), QDBusConnection::systemBus());
13 qDebug() << "check wifi p2p device" << devicePath.path();
14 if (device.deviceType() == 30)
15 {
16 qDebug() << "found wifi p2p device" << devicePath.path();
17 break;
18 }
19 }
20}117:06:43: Starting /home/planc/miracast/build-wifi-p2p-unknown-Default/wifi-p2p...
2check wifi p2p device "/org/freedesktop/NetworkManager/Devices/1"
3check wifi p2p device "/org/freedesktop/NetworkManager/Devices/2"
4check wifi p2p device "/org/freedesktop/NetworkManager/Devices/5"
5found wifi p2p device "/org/freedesktop/NetworkManager/Devices/5"
617:06:43: /home/planc/miracast/build-wifi-p2p-unknown-Default/wifi-p2p exited with code 0如果看不到日志打印可以参考 Qt 日志模块的使用 进行配置
1#! /usr/bin/env python3
2import dbus
3from dbus.proxies import ProxyObject
4import xml.dom.minidom as minidom
5from typing import Callable, List, Set, Dict
6from argparse import ArgumentParser, Namespace
7
8# 不需要的 Interface
9filter:Set[str] = {
10 "org.freedesktop.DBus.Introspectable",
11 "org.freedesktop.DBus.Peer",
12 "org.freedesktop.DBus.Properties",
13}
14
15class DBusTypeParser(object):
16 dbusQtContainerType:Dict[str,str] = {
17 "<array>": "QList",
18 "<struct>": "QVariant",
19 "<dict>": "QMap",
20 }
21
22 dbusQtType:Dict[str,str] = {
23 "y": "quint8",
24 "b": "bool",
25 "n": "qint16",
26 "q": "quint16",
27 "i": "qint32",
28 "u": "quint32",
29 "x": "qint64",
30 "t": "quint64",
31 "d": "double",
32 "h": "quint32",
33 "s": "QString",
34 "o": "QDBusObject",
35 "g": "QString",
36 "v": "QVariant",
37 }
38
39 def __init__(self) -> None:
40 # 状态
41 self.currentState:str = "<normal>"
42 self.stateStack:List[str] = []
43
44 # dict里的第几个参数
45 self.currentIndex:int = 0
46 self.indexStack:List[int] = []
47
48 def pushState(self, state:str) -> None:
49 if state == "<dict>":
50 self.pushIndex(self.currentIndex)
51 self.currentIndex = 0
52 self.stateStack.append(state)
53
54 def popState(self) -> str:
55 state:str = self.stateStack.pop()
56 if state == "<dict>":
57 self.currentIndex = self.popIndex()
58 return state
59
60 def pushIndex(self, index:int) -> None:
61 self.indexStack.append(index)
62
63 def popIndex(self) -> int:
64 return self.indexStack.pop()
65
66 def parse(self, signature:str) -> str:
67 self.currentState = "<normal>"
68 self.currentIndex = 0
69 qtype:str = ""
70 while len(signature) > 0:
71 ch:str = signature[0]
72 signature = signature[1:]
73
74 if ch in DBusTypeParser.dbusQtType:
75 if self.currentState == "<normal>":
76 qtype += self.parseNormal(ch)
77 elif self.currentState == "<struct>":
78 qtype += self.parseStruct(ch)
79 elif self.currentState == "<dict>":
80 qtype += self.parseDict(ch)
81 elif self.currentState == "<array>":
82 qtype += self.parseArray(ch)
83 elif ch == "a" and signature:
84 if self.currentState == "<dict>" and self.currentIndex == 0:
85 qtype += DBusTypeParser.dbusQtContainerType['<dict>'] + "<"
86 self.pushState(self.currentState)
87 self.currentState = "<array>"
88 elif ch == "(":
89 if self.currentState == "<dict>" and self.currentIndex == 0:
90 qtype += DBusTypeParser.dbusQtContainerType['<dict>'] + "<"
91 self.pushState(self.currentState)
92 self.currentState = "<struct>"
93 elif ch == "{":
94 # a{ 开启dict模式,之前为暂态的array模式,不push
95 self.currentState = "<dict>"
96 elif ch == ")":
97 qtype += self.parseStruct(ch)
98 if self.currentState == "<dict>" and self.currentIndex == 0:
99 qtype += ", "
100 self.currentIndex += 1
101 elif ch == "}":
102 self.currentState = self.popState()
103 qtype += self.parseDict(ch)
104 if self.currentState == "<dict>" and self.currentIndex == 0:
105 qtype += ", "
106 self.currentIndex += 1
107 else:
108 print(f"{ch}")
109 raise f"Unknown signature '{ch}'"
110 return qtype
111
112 def parseNormal(self, ch:str) -> str:
113 return DBusTypeParser.dbusQtType[ch]
114
115 def parseArray(self, ch:str) -> str:
116 self.currentState = self.popState()
117 return f"{DBusTypeParser.dbusQtContainerType['<array>']}<{DBusTypeParser.dbusQtType[ch]}>"
118
119 def parseStruct(self, ch:str) -> str:
120 if ch != ")":
121 return ""
122
123 self.currentState = self.popState()
124 if self.currentState == "<struct>":
125 return ""
126
127 if self.currentState == "<normal>":
128 return DBusTypeParser.dbusQtContainerType["<struct>"]
129
130 if self.currentState == "<dict>":
131 return DBusTypeParser.dbusQtContainerType["<struct>"]
132
133 if self.currentState == "<array>":
134 self.currentState = self.popState()
135 return f"{DBusTypeParser.dbusQtContainerType['<array>']}<{DBusTypeParser.dbusQtContainerType['<struct>']}>"
136
137 def parseDict(self, ch:str) -> str:
138 if ch == "}":
139 return ">"
140
141 self.currentIndex += 1
142 if self.currentIndex == 1:
143 return DBusTypeParser.dbusQtContainerType['<dict>'] + "<" + DBusTypeParser.dbusQtType[ch] + ", "
144 else:
145 return DBusTypeParser.dbusQtType[ch]
146
147dbusTypeParser = DBusTypeParser()
148
149parser:ArgumentParser = ArgumentParser(description='DBus Introspect XML')
150parser.add_argument("-t", "--type", default="session", help="bus type, system or session")
151parser.add_argument("-n", "--name", help="bus namae")
152parser.add_argument("-p", "--path", help="object path")
153args:Namespace = parser.parse_args()
154
155bus:dbus.Bus = dbus.SystemBus() if args.type == "system" else dbus.SessionBus()
156proxy:ProxyObject = bus.get_object(args.name, args.path)
157xmlString:str = proxy.Introspect(dbus_interface='org.freedesktop.DBus.Introspectable')
158
159root:minidom.Document = minidom.parseString(xmlString)
160interfaces:List[minidom.Element] = root.getElementsByTagName("interface")
161
162neededInterfaces:List[minidom.Element] = []
163for interface in interfaces:
164 name:str = interface.getAttribute("name")
165 if name in filter:
166 continue
167
168 methods:List[minidom.Element] = interface.getElementsByTagName("method")
169 for method in methods:
170 inIndex:int = 0
171 outIndex:int = 0
172 methodArgs:List[minidom.Element] = method.getElementsByTagName("arg")
173 for arg in methodArgs:
174 sign:str = arg.getAttribute("type")
175 qtype:str = dbusTypeParser.parse(sign)
176 annotation:minidom.Element = root.createElement("annotation")
177
178 if arg.getAttribute("direction") == "in":
179 annotation.setAttribute("name", f"org.qtproject.QtDBus.QtTypeName.In{inIndex}")
180 inIndex += 1
181 if arg.getAttribute("direction") == "out":
182 annotation.setAttribute("name", f"org.qtproject.QtDBus.QtTypeName.Out{outIndex}")
183 outIndex += 1
184
185 annotation.setAttribute("value", qtype)
186 method.appendChild(annotation)
187
188 properties = interface.getElementsByTagName("property")
189 for property in properties:
190 sign:str = property.getAttribute("type")
191 qtype:str = dbusTypeParser.parse(sign)
192 annotation:minidom.Element = root.createElement("annotation")
193 annotation.setAttribute("name", "org.qtproject.QtDBus.QtTypeName")
194 annotation.setAttribute("value", qtype)
195 property.appendChild(annotation)
196
197 neededInterfaces.append(interface)
198
199
200for interface in neededInterfaces:
201 with open(interface.getAttribute("name") + ".xml", "w") as fp:
202 fp.write(interface.toprettyxml())